package net.jalbum.filters;

import java.awt.image.BufferedImage;
import java.io.Serializable;

/**
 * Title:		JAlbum Red Eye Filter.
 * Copyright:	Copyright (c) 2007
 * @author		David Fichtmueller <david@fichtmueller.de>
 * @version		1.0
 */

public class RedEyeFilter implements Serializable {
	
	/**
	 * 
	 */
	private static final long serialVersionUID = -4523066380283659944L;
	private int radius = 20;
	private double value1 = 1.0;
	private double value2 = 0.6;
	private double valueFactor = 0.5;
	private int maxGap = 4;
	private int centerX = 0;
	private int centerY = 0;
	private int width = 0;
	private int height = 0;

	protected RedEyeFilter(){}
	
	public BufferedImage filter(BufferedImage bi) {
    	width = bi.getWidth();
    	height = bi.getHeight();
    	
    	if(centerX>width-1){centerX = width-1;}else if (centerX<0){centerX=0;}
		if(centerY>height-1){centerY = height-1;}else if (centerY<0){centerY=0;}
		if(radius<0){radius=0;}
		if(maxGap<0){maxGap=0;}

		//start left
		boolean left = checkEye(bi,centerX-1,centerY, 0, 0);
		//start up
		boolean up = checkEye(bi,centerX,centerY-1, 1, 0);
		//start right
		boolean right = checkEye(bi,centerX+1,centerY, 2, 0);
		//start up
		boolean down = checkEye(bi,centerX,centerY+1, 3, 0);
		
		if(left && up && right && down){
			colorEye(bi,centerX-1,centerY, 0, 0);
			//start up
			colorEye(bi,centerX,centerY-1, 1, 0);
			//start right
			colorEye(bi,centerX+1,centerY, 2, 0);
			//start up
			colorEye(bi,centerX,centerY+1, 3, 0);
			
		}
    	// get the rgb-values
    	int rgb=bi.getRGB(centerX,centerY);
		int alpha = (rgb & 0xff000000) >> 24;
		int r = (rgb & 0x00ff0000) >> 16;
		int g = (rgb & 0x0000ff00) >> 8;
		int b = (rgb & 0x000000ff);
		
		if(r>(g+b)*value2){
			if(r>(g+b)*value1){
    			r = g;
    		}else if(r>(g+b)*value2){
    			double factor = (r-(g+b)*value2)/((g+b)*value1-(g+b)*value2);
    			r = (int)(r*(1-factor)+g*factor);
    		}else{
    			//the value of r is not changed
    			//r = r;
    		}
			rgb = ((alpha & 0xff) << 24) | ((r & 0xff) << 16) | ((g & 0xff) << 8) | ( b & 0xff);
	    	bi.setRGB(centerX,centerY,rgb);
		}
		
    	return bi;
    }

    protected boolean checkEye(BufferedImage bi, int x,int y, int direction, int gap){
    	int distX = centerX-x;
    	int distY = centerY-y;
    	int dist = distX*distX+distY*distY;
    	int newGap = 0;
    	
    	Boolean checkPixel = checkPixel(x,y,direction,gap);
    	if(checkPixel!=null){
    		return checkPixel.booleanValue();
    	}
		
    	// get the rgb-values
    	int irgb=bi.getRGB(x,y);
		int r = (irgb & 0x00ff0000) >> 16;
		int g = (irgb & 0x0000ff00) >> 8;
		int b = (irgb & 0x000000ff);
    	
    	//evaluate own color
		boolean redPixel = false;
		
		if(r>(g+b)*(value2*0.5+value1*0.5)){
			redPixel = true;
			newGap = 0;
		}else{
			newGap = gap+1;
		}
		
    	//check distance from center
    	if(dist>radius*radius){
    		if(redPixel){
    			return false;
    		}else{
    			return true;
    		}
    	}
    	
    	//check if main axis
    	boolean goCenter = ((direction%2 == 1)&&(x==centerX))||((direction%2 == 0)&&(y==centerY));
    	boolean goLeft = ((direction==0 && centerY-y<0) || (direction==1 && centerX-x>0) || (direction==2 && centerY-y>0) || (direction==3 && centerX-x<0));
    	
		//send out in all 3 directions
		int sideX, sideY;//indicates the step to the left
		int stepX, stepY;//indicates the step in the middle
		if(direction==0){
			sideX = 0;
			sideY = 1;
			stepX = -1;
			stepY = 0;
		}else if(direction==1){
			sideX = -1;
			sideY = 0;
			stepX = 0;
			stepY = -1;
		}else if(direction==2){
			sideX = 0;
			sideY = -1;
			stepX = 1;
			stepY = 0;
		}else{//direction == 3
			sideX = 1;
			sideY = 0;
			stepX = 0;
			stepY = 1;
		}
		
		boolean left = true;
		boolean main = true;
		boolean right = true;
		
		if(goLeft || goCenter){
			left = checkEye(bi,x+sideX,y+sideY,direction,newGap);
		}
		if(goCenter){
			main = checkEye(bi,x+stepX,y+stepY,direction,newGap);
		}
		if(!goLeft || goCenter){
			right = checkEye(bi,x-sideX,y-sideY,direction,newGap);
		}
		
		boolean check = left && main && right;
    	
    	//calculate answer
    	if(check){
    		return true;
    	}else{
    		if(gap>0){
    			return true;
    		}else{
    			return false;
    		}
    	}
    	
    }
    protected Boolean checkPixel(int x,int y, int direction, int gap){
    	//evaluate gap size
    	if(gap>maxGap){
    		return new Boolean(true);
    	}
    	
    	//check bounds
    	if(x<0 || y<0 || x>width-1 || y>height-1){
    		return new Boolean(true);
    	}
    	
    	//check not one of the main axis
    	if(!((x==centerX)||(y==centerY))){
    		//check if pixel is in the right quadrant
    		double angle = Math.toDegrees(Math.atan((double)(centerY-y)/(double)(x-centerX)));
    		angle = angle+180*(0.5-((x-centerX)/Math.abs(x-centerX)*0.5));
    		if(angle<0){angle = 360+angle;};
    		
    		if(!((angle<45 && direction == 2)||(angle>=45 && angle<=135 && direction == 1)||(angle>135 && angle<225 && direction == 0)||(angle>=225 && angle<=315 && direction == 3)||(angle>315 && direction == 2))){
    			return new Boolean(true);
    		}
    	}
    	
    	return null;
    }
    
    protected boolean colorEye(BufferedImage bi, int x,int y, int direction, int gap){
    	int distX = centerX-x;
    	int distY = centerY-y;
    	int dist = distX*distX+distY*distY;
    	int newGap = 0;
    	
    	Boolean checkPixel = checkPixel(x,y,direction,gap);
    	if(checkPixel!=null){
    		return checkPixel.booleanValue();
    	}
		
    	// get the rgb-values
    	int rgb=bi.getRGB(x,y);
		int alpha = (rgb & 0xff000000) >> 24;
		int r = (rgb & 0x00ff0000) >> 16;
		int g = (rgb & 0x0000ff00) >> 8;
		int b = (rgb & 0x000000ff);
    	
    	//evaluate own color
		boolean redPixel = false;
		
		if(r>(g+b)*(value2*(valueFactor)+value1*(1-valueFactor))){
			redPixel = true;
			newGap = 0;
		}else{
			newGap = gap+1;
		}
		
    	//check distance from center
    	if(dist>radius*radius){
    		if(redPixel){
    			return false;
    		}else{
    			return true;
    		}
    	}
    	
    	//check if main axis
    	boolean goCenter = ((direction%2 == 1)&&(x==centerX))||((direction%2 == 0)&&(y==centerY));
    	boolean goLeft = ((direction==0 && centerY-y<0) || (direction==1 && centerX-x>0) || (direction==2 && centerY-y>0) || (direction==3 && centerX-x<0));
    	
		//send out in all 3 directions
		int sideX, sideY;//indecates the step to the left
		int stepX, stepY;//indecates the step in the middle
		if(direction==0){
			sideX = 0;
			sideY = 1;
			stepX = -1;
			stepY = 0;
		}else if(direction==1){
			sideX = -1;
			sideY = 0;
			stepX = 0;
			stepY = -1;
		}else if(direction==2){
			sideX = 0;
			sideY = -1;
			stepX = 1;
			stepY = 0;
		}else{//direction == 3
			sideX = 1;
			sideY = 0;
			stepX = 0;
			stepY = 1;
		}
		
		boolean left = true;
		boolean main = true;
		boolean right = true;
		
		if(goLeft || goCenter){
			left = colorEye(bi,x+sideX,y+sideY,direction,newGap);
		}
		if(goCenter){
			main = colorEye(bi,x+stepX,y+stepY,direction,newGap);
		}
		if(!goLeft || goCenter){
			right = colorEye(bi,x-sideX,y-sideY,direction,newGap);
		}
		
		boolean check = left && main && right;
    	
    	//calculate answer
    	if(check){
    		if(r>(g+b)*value2){
    			if(r>(g+b)*value1){
        			r = g;
        		}else if(r>(g+b)*value2){
        			double factor = (r-(g+b)*value2)/((g+b)*value1-(g+b)*value2);
        			r = (int)(r*(1-factor)+g*factor);
        		}else{
        			//the value of r is not changed
        			//r = r;
        		}
    			rgb = ((alpha & 0xff) << 24) | ((r & 0xff) << 16) | ((g & 0xff) << 8) | ( b & 0xff);
		    	bi.setRGB(x,y,rgb);
    		}
    		return true;
    	}else{
    		if(gap>0){
    			return true;
    		}else{
    			return false;
    		}
    	}
    }
    
    public int getRadius() {
		return radius;
	}

	public void setRadius(int radius) {
		this.radius = radius;
	}

	public int getX() {
		return centerX;
	}

	public void setX(int x) {
		this.centerX = x;
	}

	public int getY() {
		return centerY;
	}

	public void setY(int y) {
		this.centerY = y;
	}

	public double getValue1() {
		return value1;
	}

	public void setValue1(double value1) {
		this.value1 = value1;
	}

	public double getValue2() {
		return value2;
	}

	public void setValue2(double value2) {
		this.value2 = value2;
	}

	public int getMaxGap() {
		return maxGap;
	}

	public void setMaxGap(int maxGap) {
		this.maxGap = maxGap;
	}

	public double getValueFactor() {
		return valueFactor;
	}

	public void setValueFactor(double valueFactor) {
		this.valueFactor = valueFactor;
	}

	public int getCenterX() {
		return centerX;
	}

	public void setCenterX(int centerX) {
		this.centerX = centerX;
	}

	public int getCenterY() {
		return centerY;
	}

	public void setCenterY(int centerY) {
		this.centerY = centerY;
	}

	public int getWidth() {
		return width;
	}

	public void setWidth(int width) {
		this.width = width;
	}

	public int getHeight() {
		return height;
	}

	public void setHeight(int height) {
		this.height = height;
	}

}
